/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use std::{hash, ops, u32, usize};

use api::{
    units::*, BorderRadius, ClipMode, ColorF, ColorU, FillRule, ImageRendering,
    PremultipliedColorF, PrimitiveFlags, PrimitiveKeyKind, PropertyBinding, RasterSpace,
    RepeatMode, Shadow, POLYGON_CLIP_VERTEX_MAX,
};
use euclid::{SideOffsets2D, Size2D};
use glyph_rasterizer::GlyphKey;

use crate::{
    border::BorderSegmentCacheKey,
    clip::ClipLeafId,
    composite::CompositorSurfaceKind,
    debug_colors,
    debug_item::{DebugItem, DebugMessage},
    frame_builder::FrameBuildingState,
    gpu_cache::{GpuCacheAddress, GpuCacheHandle, GpuDataRequest},
    gpu_types::{BrushFlags, QuadSegment},
    intern,
    internal_types::{FastHashSet, LayoutPrimitiveInfo},
    pattern::{Pattern, PatternBuilder, PatternBuilderContext, PatternBuilderState},
    picture::PicturePrimitive,
    quad::QuadTileClassifier,
    render_task_graph::RenderTaskId,
    resource_cache::ImageProperties,
    scene::SceneProperties,
    scene_building::{CreateShadow, IsVisible},
    segment::EdgeAaSegmentMask,
    util::Recycler,
    visibility::PrimitiveVisibility,
};

pub mod backdrop;
pub mod borders;
pub mod gradient;
pub mod image;
pub mod interned;
pub mod line_dec;
pub mod picture;
pub mod text_run;

mod storage;

use backdrop::{BackdropCaptureDataHandle, BackdropRenderDataHandle};
use borders::{ImageBorderDataHandle, NormalBorderDataHandle};
use gradient::{
    ConicGradientDataHandle, LinearGradientDataHandle, LinearGradientPrimitive,
    RadialGradientDataHandle,
};
use image::{ImageDataHandle, ImageInstance, YuvImageDataHandle};
use line_dec::LineDecorationDataHandle;
use picture::PictureDataHandle;
use text_run::{TextRunDataHandle, TextRunPrimitive};

use crate::box_shadow::BoxShadowDataHandle;

pub const VECS_PER_SEGMENT: usize = 2;

#[derive(Debug, Copy, Clone)]
pub struct PrimitiveOpacity {
    pub is_opaque: bool,
}

impl PrimitiveOpacity {
    pub fn opaque() -> PrimitiveOpacity {
        PrimitiveOpacity { is_opaque: true }
    }

    pub fn translucent() -> PrimitiveOpacity {
        PrimitiveOpacity { is_opaque: false }
    }

    pub fn from_alpha(alpha: f32) -> PrimitiveOpacity {
        PrimitiveOpacity {
            is_opaque: alpha >= 1.0,
        }
    }
}

/// For external images, it's not possible to know the
/// UV coords of the image (or the image data itself)
/// until the render thread receives the frame and issues
/// callbacks to the client application. For external
/// images that are visible, a DeferredResolve is created
/// that is stored in the frame. This allows the render
/// thread to iterate this list and update any changed
/// texture data and update the UV rect. Any filtering
/// is handled externally for NativeTexture external
/// images.
pub struct DeferredResolve {
    pub address: GpuCacheAddress,
    pub image_properties: ImageProperties,
    pub rendering: ImageRendering,
}

#[derive(Debug, Copy, Clone, PartialEq)]
pub struct ClipTaskIndex(pub u32);

impl ClipTaskIndex {
    pub const INVALID: ClipTaskIndex = ClipTaskIndex(0);
}

#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct PictureIndex(pub usize);

impl PictureIndex {
    pub const INVALID: PictureIndex = PictureIndex(!0);
}

#[derive(Copy, Debug, Clone, PartialEq)]
pub struct RectangleKey {
    pub x0: f32,
    pub y0: f32,
    pub x1: f32,
    pub y1: f32,
}

impl RectangleKey {
    pub fn intersects(&self, other: &Self) -> bool {
        self.x0 < other.x1 && other.x0 < self.x1 && self.y0 < other.y1 && other.y0 < self.y1
    }
}

impl Eq for RectangleKey {}

impl hash::Hash for RectangleKey {
    fn hash<H: hash::Hasher>(&self, state: &mut H) {
        self.x0.to_bits().hash(state);
        self.y0.to_bits().hash(state);
        self.x1.to_bits().hash(state);
        self.y1.to_bits().hash(state);
    }
}

impl From<RectangleKey> for LayoutRect {
    fn from(key: RectangleKey) -> LayoutRect {
        LayoutRect {
            min: LayoutPoint::new(key.x0, key.y0),
            max: LayoutPoint::new(key.x1, key.y1),
        }
    }
}

impl From<RectangleKey> for WorldRect {
    fn from(key: RectangleKey) -> WorldRect {
        WorldRect {
            min: WorldPoint::new(key.x0, key.y0),
            max: WorldPoint::new(key.x1, key.y1),
        }
    }
}

impl From<LayoutRect> for RectangleKey {
    fn from(rect: LayoutRect) -> RectangleKey {
        RectangleKey {
            x0: rect.min.x,
            y0: rect.min.y,
            x1: rect.max.x,
            y1: rect.max.y,
        }
    }
}

impl From<PictureRect> for RectangleKey {
    fn from(rect: PictureRect) -> RectangleKey {
        RectangleKey {
            x0: rect.min.x,
            y0: rect.min.y,
            x1: rect.max.x,
            y1: rect.max.y,
        }
    }
}

impl From<WorldRect> for RectangleKey {
    fn from(rect: WorldRect) -> RectangleKey {
        RectangleKey {
            x0: rect.min.x,
            y0: rect.min.y,
            x1: rect.max.x,
            y1: rect.max.y,
        }
    }
}

/// To create a fixed-size representation of a polygon, we use a fixed
/// number of points. Our initialization method restricts us to values
/// <= 32. If our constant POLYGON_CLIP_VERTEX_MAX is > 32, the Rust
/// compiler will complain.
#[derive(Copy, Debug, Clone, Hash, PartialEq)]
pub struct PolygonKey {
    pub point_count: u8,
    pub points: [PointKey; POLYGON_CLIP_VERTEX_MAX],
    pub fill_rule: FillRule,
}

impl PolygonKey {
    pub fn new(points_layout: &Vec<LayoutPoint>, fill_rule: FillRule) -> Self {
        // We have to fill fixed-size arrays with data from a Vec.
        // We'll do this by initializing the arrays to known-good
        // values then overwriting those values as long as our
        // iterator provides values.
        let mut points: [PointKey; POLYGON_CLIP_VERTEX_MAX] =
            [PointKey { x: 0.0, y: 0.0 }; POLYGON_CLIP_VERTEX_MAX];

        let mut point_count: u8 = 0;
        for (src, dest) in points_layout.iter().zip(points.iter_mut()) {
            *dest = (*src as LayoutPoint).into();
            point_count = point_count + 1;
        }

        PolygonKey {
            point_count,
            points,
            fill_rule,
        }
    }
}

impl Eq for PolygonKey {}

/// A hashable SideOffset2D that can be used in primitive keys.
#[derive(Debug, Clone, PartialEq)]
pub struct SideOffsetsKey {
    pub top: f32,
    pub right: f32,
    pub bottom: f32,
    pub left: f32,
}

impl Eq for SideOffsetsKey {}

impl hash::Hash for SideOffsetsKey {
    fn hash<H: hash::Hasher>(&self, state: &mut H) {
        self.top.to_bits().hash(state);
        self.right.to_bits().hash(state);
        self.bottom.to_bits().hash(state);
        self.left.to_bits().hash(state);
    }
}

impl From<SideOffsetsKey> for LayoutSideOffsets {
    fn from(key: SideOffsetsKey) -> LayoutSideOffsets {
        LayoutSideOffsets::new(key.top, key.right, key.bottom, key.left)
    }
}

impl<U> From<SideOffsets2D<f32, U>> for SideOffsetsKey {
    fn from(offsets: SideOffsets2D<f32, U>) -> SideOffsetsKey {
        SideOffsetsKey {
            top: offsets.top,
            right: offsets.right,
            bottom: offsets.bottom,
            left: offsets.left,
        }
    }
}

/// A hashable size for using as a key during primitive interning.
#[derive(Copy, Debug, Clone, PartialEq)]
pub struct SizeKey {
    w: f32,
    h: f32,
}

impl Eq for SizeKey {}

impl hash::Hash for SizeKey {
    fn hash<H: hash::Hasher>(&self, state: &mut H) {
        self.w.to_bits().hash(state);
        self.h.to_bits().hash(state);
    }
}

impl From<SizeKey> for LayoutSize {
    fn from(key: SizeKey) -> LayoutSize {
        LayoutSize::new(key.w, key.h)
    }
}

impl<U> From<Size2D<f32, U>> for SizeKey {
    fn from(size: Size2D<f32, U>) -> SizeKey {
        SizeKey {
            w: size.width,
            h: size.height,
        }
    }
}

/// A hashable vec for using as a key during primitive interning.
#[derive(Copy, Debug, Clone, PartialEq)]
pub struct VectorKey {
    pub x: f32,
    pub y: f32,
}

impl Eq for VectorKey {}

impl hash::Hash for VectorKey {
    fn hash<H: hash::Hasher>(&self, state: &mut H) {
        self.x.to_bits().hash(state);
        self.y.to_bits().hash(state);
    }
}

impl From<VectorKey> for LayoutVector2D {
    fn from(key: VectorKey) -> LayoutVector2D {
        LayoutVector2D::new(key.x, key.y)
    }
}

impl From<VectorKey> for WorldVector2D {
    fn from(key: VectorKey) -> WorldVector2D {
        WorldVector2D::new(key.x, key.y)
    }
}

impl From<LayoutVector2D> for VectorKey {
    fn from(vec: LayoutVector2D) -> VectorKey {
        VectorKey { x: vec.x, y: vec.y }
    }
}

impl From<WorldVector2D> for VectorKey {
    fn from(vec: WorldVector2D) -> VectorKey {
        VectorKey { x: vec.x, y: vec.y }
    }
}

/// A hashable point for using as a key during primitive interning.
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct PointKey {
    pub x: f32,
    pub y: f32,
}

impl Eq for PointKey {}

impl hash::Hash for PointKey {
    fn hash<H: hash::Hasher>(&self, state: &mut H) {
        self.x.to_bits().hash(state);
        self.y.to_bits().hash(state);
    }
}

impl From<PointKey> for LayoutPoint {
    fn from(key: PointKey) -> LayoutPoint {
        LayoutPoint::new(key.x, key.y)
    }
}

impl From<LayoutPoint> for PointKey {
    fn from(p: LayoutPoint) -> PointKey {
        PointKey { x: p.x, y: p.y }
    }
}

impl From<PicturePoint> for PointKey {
    fn from(p: PicturePoint) -> PointKey {
        PointKey { x: p.x, y: p.y }
    }
}

impl From<WorldPoint> for PointKey {
    fn from(p: WorldPoint) -> PointKey {
        PointKey { x: p.x, y: p.y }
    }
}

/// A hashable float for using as a key during primitive interning.
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct FloatKey(f32);

impl Eq for FloatKey {}

impl hash::Hash for FloatKey {
    fn hash<H: hash::Hasher>(&self, state: &mut H) {
        self.0.to_bits().hash(state);
    }
}

#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct PrimKeyCommonData {
    pub flags: PrimitiveFlags,
    pub prim_rect: RectangleKey,
}

impl From<&LayoutPrimitiveInfo> for PrimKeyCommonData {
    fn from(info: &LayoutPrimitiveInfo) -> Self {
        PrimKeyCommonData {
            flags: info.flags,
            prim_rect: info.rect.into(),
        }
    }
}

#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct PrimKey<T> {
    pub common: PrimKeyCommonData,
    pub kind: T,
}

#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct PrimitiveKey {
    pub common: PrimKeyCommonData,
    pub kind: PrimitiveKeyKind,
}

impl PrimitiveKey {
    pub fn new(info: &LayoutPrimitiveInfo, kind: PrimitiveKeyKind) -> Self {
        PrimitiveKey {
            common: info.into(),
            kind,
        }
    }
}

impl intern::InternDebug for PrimitiveKey {}

/// The shared information for a given primitive. This is interned and retained
/// both across frames and display lists, by comparing the matching PrimitiveKey.

pub enum PrimitiveTemplateKind {
    Rectangle { color: PropertyBinding<ColorF> },
    Clear,
}

impl PrimitiveTemplateKind {
    /// Write any GPU blocks for the primitive template to the given request object.
    pub fn write_prim_gpu_blocks(
        &self,
        request: &mut GpuDataRequest,
        scene_properties: &SceneProperties,
    ) {
        match *self {
            PrimitiveTemplateKind::Clear => {
                // Opaque black with operator dest out
                request.push(PremultipliedColorF::BLACK);
            }
            PrimitiveTemplateKind::Rectangle { ref color, .. } => {
                request.push(scene_properties.resolve_color(color).premultiplied())
            }
        }
    }
}

/// Construct the primitive template data from a primitive key. This
/// is invoked when a primitive key is created and the interner
/// doesn't currently contain a primitive with this key.
impl From<PrimitiveKeyKind> for PrimitiveTemplateKind {
    fn from(kind: PrimitiveKeyKind) -> Self {
        match kind {
            PrimitiveKeyKind::Clear => PrimitiveTemplateKind::Clear,
            PrimitiveKeyKind::Rectangle { color, .. } => PrimitiveTemplateKind::Rectangle {
                color: color.into(),
            },
        }
    }
}

#[derive(Debug)]
pub struct PrimTemplateCommonData {
    pub flags: PrimitiveFlags,
    pub may_need_repetition: bool,
    pub prim_rect: LayoutRect,
    pub opacity: PrimitiveOpacity,
    /// The GPU cache handle for a primitive template. Since this structure
    /// is retained across display lists by interning, this GPU cache handle
    /// also remains valid, which reduces the number of updates to the GPU
    /// cache when a new display list is processed.
    pub gpu_cache_handle: GpuCacheHandle,
    /// Specifies the edges that are *allowed* to have anti-aliasing.
    /// In other words EdgeAaSegmentFlags::all() does not necessarily mean all edges will
    /// be anti-aliased, only that they could be.
    ///
    /// Use this to force disable anti-alasing on edges of the primitives.
    pub edge_aa_mask: EdgeAaSegmentMask,
}

impl PrimTemplateCommonData {
    pub fn with_key_common(common: PrimKeyCommonData) -> Self {
        PrimTemplateCommonData {
            flags: common.flags,
            may_need_repetition: true,
            prim_rect: common.prim_rect.into(),
            gpu_cache_handle: GpuCacheHandle::new(),
            opacity: PrimitiveOpacity::translucent(),
            edge_aa_mask: EdgeAaSegmentMask::all(),
        }
    }
}

pub struct PrimTemplate<T> {
    pub common: PrimTemplateCommonData,
    pub kind: T,
}

pub struct PrimitiveTemplate {
    pub common: PrimTemplateCommonData,
    pub kind: PrimitiveTemplateKind,
}

impl PatternBuilder for PrimitiveTemplate {
    fn build(
        &self,
        _sub_rect: Option<DeviceRect>,
        ctx: &PatternBuilderContext,
        _state: &mut PatternBuilderState,
    ) -> crate::pattern::Pattern {
        match self.kind {
            PrimitiveTemplateKind::Clear => Pattern::clear(),
            PrimitiveTemplateKind::Rectangle { ref color, .. } => {
                let color = ctx.scene_properties.resolve_color(color);
                Pattern::color(color)
            }
        }
    }

    fn get_base_color(&self, ctx: &PatternBuilderContext) -> ColorF {
        match self.kind {
            PrimitiveTemplateKind::Clear => ColorF::BLACK,
            PrimitiveTemplateKind::Rectangle { ref color, .. } => {
                ctx.scene_properties.resolve_color(color)
            }
        }
    }

    fn use_shared_pattern(&self) -> bool {
        true
    }
}

impl ops::Deref for PrimitiveTemplate {
    type Target = PrimTemplateCommonData;
    fn deref(&self) -> &Self::Target {
        &self.common
    }
}

impl ops::DerefMut for PrimitiveTemplate {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.common
    }
}

impl From<PrimitiveKey> for PrimitiveTemplate {
    fn from(item: PrimitiveKey) -> Self {
        PrimitiveTemplate {
            common: PrimTemplateCommonData::with_key_common(item.common),
            kind: item.kind.into(),
        }
    }
}

impl PrimitiveTemplate {
    /// Update the GPU cache for a given primitive template. This may be called multiple
    /// times per frame, by each primitive reference that refers to this interned
    /// template. The initial request call to the GPU cache ensures that work is only
    /// done if the cache entry is invalid (due to first use or eviction).
    pub fn update(
        &mut self,
        frame_state: &mut FrameBuildingState,
        scene_properties: &SceneProperties,
    ) {
        if let Some(mut request) = frame_state
            .gpu_cache
            .request(&mut self.common.gpu_cache_handle)
        {
            self.kind
                .write_prim_gpu_blocks(&mut request, scene_properties);
        }

        self.opacity = match self.kind {
            PrimitiveTemplateKind::Clear => PrimitiveOpacity::translucent(),
            PrimitiveTemplateKind::Rectangle { ref color, .. } => {
                PrimitiveOpacity::from_alpha(scene_properties.resolve_color(color).a)
            }
        };
    }
}

type PrimitiveDataHandle = intern::Handle<PrimitiveKeyKind>;

impl intern::Internable for PrimitiveKeyKind {
    type Key = PrimitiveKey;
    type StoreData = PrimitiveTemplate;
    type InternData = ();
    const PROFILE_COUNTER: usize = crate::profiler::INTERNED_PRIMITIVES;
}

impl InternablePrimitive for PrimitiveKeyKind {
    fn into_key(self, info: &LayoutPrimitiveInfo) -> PrimitiveKey {
        PrimitiveKey::new(info, self)
    }

    fn make_instance_kind(
        key: PrimitiveKey,
        data_handle: PrimitiveDataHandle,
        prim_store: &mut PrimitiveStore,
    ) -> PrimitiveInstanceKind {
        match key.kind {
            PrimitiveKeyKind::Clear => PrimitiveInstanceKind::Clear { data_handle },
            PrimitiveKeyKind::Rectangle { color, .. } => {
                let color_binding_index = match color {
                    PropertyBinding::Binding(..) => prim_store.color_bindings.push(color),
                    PropertyBinding::Value(..) => ColorBindingIndex::INVALID,
                };
                PrimitiveInstanceKind::Rectangle {
                    data_handle,
                    segment_instance_index: SegmentInstanceIndex::INVALID,
                    color_binding_index,
                    use_legacy_path: false,
                }
            }
        }
    }
}

#[derive(Debug)]
pub struct VisibleMaskImageTile {
    pub tile_offset: TileOffset,
    pub tile_rect: LayoutRect,
    pub task_id: RenderTaskId,
}

#[derive(Debug)]
pub struct VisibleGradientTile {
    pub handle: GpuCacheHandle,
    pub local_rect: LayoutRect,
    pub local_clip_rect: LayoutRect,
}

/// Information about how to cache a border segment,
/// along with the current render task cache entry.
#[derive(Debug)]
pub struct BorderSegmentInfo {
    pub local_task_size: LayoutSize,
    pub cache_key: BorderSegmentCacheKey,
}

/// Represents the visibility state of a segment (wrt clip masks).
#[derive(Debug, Clone)]
pub enum ClipMaskKind {
    /// The segment has a clip mask, specified by the render task.
    Mask(RenderTaskId),
    /// The segment has no clip mask.
    None,
    /// The segment is made invisible / clipped completely.
    Clipped,
}

#[derive(Debug, Clone)]
pub struct BrushSegment {
    pub local_rect: LayoutRect,
    pub may_need_clip_mask: bool,
    pub edge_flags: EdgeAaSegmentMask,
    pub extra_data: [f32; 4],
    pub brush_flags: BrushFlags,
}

impl BrushSegment {
    pub fn new(
        local_rect: LayoutRect,
        may_need_clip_mask: bool,
        edge_flags: EdgeAaSegmentMask,
        extra_data: [f32; 4],
        brush_flags: BrushFlags,
    ) -> Self {
        Self {
            local_rect,
            may_need_clip_mask,
            edge_flags,
            extra_data,
            brush_flags,
        }
    }
}

#[derive(Debug, Clone)]
#[repr(C)]
struct ClipRect {
    rect: LayoutRect,
    mode: f32,
}

#[derive(Debug, Clone)]
#[repr(C)]
struct ClipCorner {
    rect: LayoutRect,
    outer_radius_x: f32,
    outer_radius_y: f32,
    inner_radius_x: f32,
    inner_radius_y: f32,
}

impl ClipCorner {
    fn uniform(rect: LayoutRect, outer_radius: f32, inner_radius: f32) -> ClipCorner {
        ClipCorner {
            rect,
            outer_radius_x: outer_radius,
            outer_radius_y: outer_radius,
            inner_radius_x: inner_radius,
            inner_radius_y: inner_radius,
        }
    }
}

#[derive(Debug, Clone)]
#[repr(C)]
pub struct ClipData {
    rect: ClipRect,
    top_left: ClipCorner,
    top_right: ClipCorner,
    bottom_left: ClipCorner,
    bottom_right: ClipCorner,
}

impl ClipData {
    pub fn rounded_rect(size: LayoutSize, radii: &BorderRadius, mode: ClipMode) -> ClipData {
        // TODO(gw): For simplicity, keep most of the clip GPU structs the
        //           same as they were, even though the origin is now always
        //           zero, since they are in the clip's local space. In future,
        //           we could reduce the GPU cache size of ClipData.
        let rect = LayoutRect::from_size(size);

        ClipData {
            rect: ClipRect {
                rect,
                mode: mode as u32 as f32,
            },
            top_left: ClipCorner {
                rect: LayoutRect::from_origin_and_size(
                    LayoutPoint::new(rect.min.x, rect.min.y),
                    LayoutSize::new(radii.top_left.width, radii.top_left.height),
                ),
                outer_radius_x: radii.top_left.width,
                outer_radius_y: radii.top_left.height,
                inner_radius_x: 0.0,
                inner_radius_y: 0.0,
            },
            top_right: ClipCorner {
                rect: LayoutRect::from_origin_and_size(
                    LayoutPoint::new(rect.max.x - radii.top_right.width, rect.min.y),
                    LayoutSize::new(radii.top_right.width, radii.top_right.height),
                ),
                outer_radius_x: radii.top_right.width,
                outer_radius_y: radii.top_right.height,
                inner_radius_x: 0.0,
                inner_radius_y: 0.0,
            },
            bottom_left: ClipCorner {
                rect: LayoutRect::from_origin_and_size(
                    LayoutPoint::new(rect.min.x, rect.max.y - radii.bottom_left.height),
                    LayoutSize::new(radii.bottom_left.width, radii.bottom_left.height),
                ),
                outer_radius_x: radii.bottom_left.width,
                outer_radius_y: radii.bottom_left.height,
                inner_radius_x: 0.0,
                inner_radius_y: 0.0,
            },
            bottom_right: ClipCorner {
                rect: LayoutRect::from_origin_and_size(
                    LayoutPoint::new(
                        rect.max.x - radii.bottom_right.width,
                        rect.max.y - radii.bottom_right.height,
                    ),
                    LayoutSize::new(radii.bottom_right.width, radii.bottom_right.height),
                ),
                outer_radius_x: radii.bottom_right.width,
                outer_radius_y: radii.bottom_right.height,
                inner_radius_x: 0.0,
                inner_radius_y: 0.0,
            },
        }
    }

    pub fn uniform(size: LayoutSize, radius: f32, mode: ClipMode) -> ClipData {
        // TODO(gw): For simplicity, keep most of the clip GPU structs the
        //           same as they were, even though the origin is now always
        //           zero, since they are in the clip's local space. In future,
        //           we could reduce the GPU cache size of ClipData.
        let rect = LayoutRect::from_size(size);

        ClipData {
            rect: ClipRect {
                rect,
                mode: mode as u32 as f32,
            },
            top_left: ClipCorner::uniform(
                LayoutRect::from_origin_and_size(
                    LayoutPoint::new(rect.min.x, rect.min.y),
                    LayoutSize::new(radius, radius),
                ),
                radius,
                0.0,
            ),
            top_right: ClipCorner::uniform(
                LayoutRect::from_origin_and_size(
                    LayoutPoint::new(rect.max.x - radius, rect.min.y),
                    LayoutSize::new(radius, radius),
                ),
                radius,
                0.0,
            ),
            bottom_left: ClipCorner::uniform(
                LayoutRect::from_origin_and_size(
                    LayoutPoint::new(rect.min.x, rect.max.y - radius),
                    LayoutSize::new(radius, radius),
                ),
                radius,
                0.0,
            ),
            bottom_right: ClipCorner::uniform(
                LayoutRect::from_origin_and_size(
                    LayoutPoint::new(rect.max.x - radius, rect.max.y - radius),
                    LayoutSize::new(radius, radius),
                ),
                radius,
                0.0,
            ),
        }
    }
}

/// A hashable descriptor for nine-patches, used by image and
/// gradient borders.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct NinePatchDescriptor {
    pub width: i32,
    pub height: i32,
    pub slice: DeviceIntSideOffsets,
    pub fill: bool,
    pub repeat_horizontal: RepeatMode,
    pub repeat_vertical: RepeatMode,
    pub widths: SideOffsetsKey,
}

impl IsVisible for PrimitiveKeyKind {
    // Return true if the primary primitive is visible.
    // Used to trivially reject non-visible primitives.
    // TODO(gw): Currently, primitives other than those
    //           listed here are handled before the
    //           add_primitive() call. In the future
    //           we should move the logic for all other
    //           primitive types to use this.
    fn is_visible(&self) -> bool {
        match *self {
            PrimitiveKeyKind::Clear => true,
            PrimitiveKeyKind::Rectangle { ref color, .. } => match *color {
                PropertyBinding::Value(value) => value.a > 0,
                PropertyBinding::Binding(..) => true,
            },
        }
    }
}

impl CreateShadow for PrimitiveKeyKind {
    // Create a clone of this PrimitiveContainer, applying whatever
    // changes are necessary to the primitive to support rendering
    // it as part of the supplied shadow.
    fn create_shadow(&self, shadow: &Shadow, _: bool, _: RasterSpace) -> PrimitiveKeyKind {
        match *self {
            PrimitiveKeyKind::Rectangle { .. } => PrimitiveKeyKind::Rectangle {
                color: PropertyBinding::Value(shadow.color.into()),
            },
            PrimitiveKeyKind::Clear => {
                panic!("bug: this prim is not supported in shadow contexts");
            }
        }
    }
}

#[derive(Debug)]
pub enum PrimitiveInstanceKind {
    /// Direct reference to a Picture
    Picture {
        /// Handle to the common interned data for this primitive.
        data_handle: PictureDataHandle,
        pic_index: PictureIndex,
    },
    /// A run of glyphs, with associated font parameters.
    TextRun {
        /// Handle to the common interned data for this primitive.
        data_handle: TextRunDataHandle,
        /// Index to the per instance scratch data for this primitive.
        run_index: TextRunIndex,
    },
    /// A line decoration. cache_handle refers to a cached render
    /// task handle, if this line decoration is not a simple solid.
    LineDecoration {
        /// Handle to the common interned data for this primitive.
        data_handle: LineDecorationDataHandle,
        // TODO(gw): For now, we need to store some information in
        //           the primitive instance that is created during
        //           prepare_prims and read during the batching pass.
        //           Once we unify the prepare_prims and batching to
        //           occur at the same time, we can remove most of
        //           the things we store here in the instance, and
        //           use them directly. This will remove cache_handle,
        //           but also the opacity, clip_task_id etc below.
        render_task: Option<RenderTaskId>,
    },
    NormalBorder {
        /// Handle to the common interned data for this primitive.
        data_handle: NormalBorderDataHandle,
        render_task_ids: storage::Range<RenderTaskId>,
    },
    ImageBorder {
        /// Handle to the common interned data for this primitive.
        data_handle: ImageBorderDataHandle,
    },
    Rectangle {
        /// Handle to the common interned data for this primitive.
        data_handle: PrimitiveDataHandle,
        segment_instance_index: SegmentInstanceIndex,
        color_binding_index: ColorBindingIndex,
        use_legacy_path: bool,
    },
    YuvImage {
        /// Handle to the common interned data for this primitive.
        data_handle: YuvImageDataHandle,
        segment_instance_index: SegmentInstanceIndex,
        compositor_surface_kind: CompositorSurfaceKind,
    },
    Image {
        /// Handle to the common interned data for this primitive.
        data_handle: ImageDataHandle,
        image_instance_index: ImageInstanceIndex,
        compositor_surface_kind: CompositorSurfaceKind,
    },
    /// Always rendered directly into the picture. This tends to be
    /// faster with SWGL.
    LinearGradient {
        /// Handle to the common interned data for this primitive.
        data_handle: LinearGradientDataHandle,
        visible_tiles_range: GradientTileRange,
    },
    /// Always rendered via a cached render task. Usually faster with
    /// a GPU.
    CachedLinearGradient {
        /// Handle to the common interned data for this primitive.
        data_handle: LinearGradientDataHandle,
        visible_tiles_range: GradientTileRange,
    },
    RadialGradient {
        /// Handle to the common interned data for this primitive.
        data_handle: RadialGradientDataHandle,
        visible_tiles_range: GradientTileRange,
        cached: bool,
    },
    ConicGradient {
        /// Handle to the common interned data for this primitive.
        data_handle: ConicGradientDataHandle,
        visible_tiles_range: GradientTileRange,
        cached: bool,
    },
    /// Clear out a rect, used for special effects.
    Clear {
        /// Handle to the common interned data for this primitive.
        data_handle: PrimitiveDataHandle,
    },
    /// Render a portion of a specified backdrop.
    BackdropCapture {
        data_handle: BackdropCaptureDataHandle,
    },
    BackdropRender {
        data_handle: BackdropRenderDataHandle,
        pic_index: PictureIndex,
    },
    BoxShadow {
        data_handle: BoxShadowDataHandle,
    },
}

impl PrimitiveInstanceKind {
    pub fn as_pic(&self) -> PictureIndex {
        match self {
            PrimitiveInstanceKind::Picture { pic_index, .. } => *pic_index,
            _ => panic!("bug: as_pic called on a prim that is not a picture"),
        }
    }
}

#[derive(Debug, Copy, Clone)]
pub struct PrimitiveInstanceIndex(pub u32);

#[derive(Debug)]
pub struct PrimitiveInstance {
    /// Identifies the kind of primitive this
    /// instance is, and references to where
    /// the relevant information for the primitive
    /// can be found.
    pub kind: PrimitiveInstanceKind,

    /// All information and state related to clip(s) for this primitive
    pub clip_leaf_id: ClipLeafId,

    /// Information related to the current visibility state of this
    /// primitive.
    // TODO(gw): Currently built each frame, but can be retained.
    pub vis: PrimitiveVisibility,
}

impl PrimitiveInstance {
    pub fn new(kind: PrimitiveInstanceKind, clip_leaf_id: ClipLeafId) -> Self {
        PrimitiveInstance {
            kind,
            vis: PrimitiveVisibility::new(),
            clip_leaf_id,
        }
    }

    // Reset any pre-frame state for this primitive.
    pub fn reset(&mut self) {
        self.vis.reset();
    }

    pub fn clear_visibility(&mut self) {
        self.vis.reset();
    }

    pub fn uid(&self) -> intern::ItemUid {
        match &self.kind {
            PrimitiveInstanceKind::Clear { data_handle, .. }
            | PrimitiveInstanceKind::Rectangle { data_handle, .. } => data_handle.uid(),
            PrimitiveInstanceKind::Image { data_handle, .. } => data_handle.uid(),
            PrimitiveInstanceKind::ImageBorder { data_handle, .. } => data_handle.uid(),
            PrimitiveInstanceKind::LineDecoration { data_handle, .. } => data_handle.uid(),
            PrimitiveInstanceKind::LinearGradient { data_handle, .. } => data_handle.uid(),
            PrimitiveInstanceKind::CachedLinearGradient { data_handle, .. } => data_handle.uid(),
            PrimitiveInstanceKind::NormalBorder { data_handle, .. } => data_handle.uid(),
            PrimitiveInstanceKind::Picture { data_handle, .. } => data_handle.uid(),
            PrimitiveInstanceKind::RadialGradient { data_handle, .. } => data_handle.uid(),
            PrimitiveInstanceKind::ConicGradient { data_handle, .. } => data_handle.uid(),
            PrimitiveInstanceKind::TextRun { data_handle, .. } => data_handle.uid(),
            PrimitiveInstanceKind::YuvImage { data_handle, .. } => data_handle.uid(),
            PrimitiveInstanceKind::BackdropCapture { data_handle, .. } => data_handle.uid(),
            PrimitiveInstanceKind::BackdropRender { data_handle, .. } => data_handle.uid(),
            PrimitiveInstanceKind::BoxShadow { data_handle, .. } => data_handle.uid(),
        }
    }
}

#[derive(Debug)]
pub struct SegmentedInstance {
    pub gpu_cache_handle: GpuCacheHandle,
    pub segments_range: SegmentsRange,
}

pub type GlyphKeyStorage = storage::Storage<GlyphKey>;
pub type TextRunIndex = storage::Index<TextRunPrimitive>;
pub type TextRunStorage = storage::Storage<TextRunPrimitive>;
pub type ColorBindingIndex = storage::Index<PropertyBinding<ColorU>>;
pub type ColorBindingStorage = storage::Storage<PropertyBinding<ColorU>>;
pub type BorderHandleStorage = storage::Storage<RenderTaskId>;
pub type SegmentStorage = storage::Storage<BrushSegment>;
pub type SegmentsRange = storage::Range<BrushSegment>;
pub type SegmentInstanceStorage = storage::Storage<SegmentedInstance>;
pub type SegmentInstanceIndex = storage::Index<SegmentedInstance>;
pub type ImageInstanceStorage = storage::Storage<ImageInstance>;
pub type ImageInstanceIndex = storage::Index<ImageInstance>;
pub type GradientTileStorage = storage::Storage<VisibleGradientTile>;
pub type GradientTileRange = storage::Range<VisibleGradientTile>;
pub type LinearGradientStorage = storage::Storage<LinearGradientPrimitive>;

/// Contains various vecs of data that is used only during frame building,
/// where we want to recycle the memory each new display list, to avoid constantly
/// re-allocating and moving memory around. Written during primitive preparation,
/// and read during batching.
pub struct PrimitiveScratchBuffer {
    /// Contains a list of clip mask instance parameters
    /// per segment generated.
    pub clip_mask_instances: Vec<ClipMaskKind>,

    /// List of glyphs keys that are allocated by each
    /// text run instance.
    pub glyph_keys: GlyphKeyStorage,

    /// List of render task handles for border segment instances
    /// that have been added this frame.
    pub border_cache_handles: BorderHandleStorage,

    /// A list of brush segments that have been built for this scene.
    pub segments: SegmentStorage,

    /// A list of segment ranges and GPU cache handles for prim instances
    /// that have opted into segment building. In future, this should be
    /// removed in favor of segment building during primitive interning.
    pub segment_instances: SegmentInstanceStorage,

    /// A list of visible tiles that tiled gradients use to store
    /// per-tile information.
    pub gradient_tiles: GradientTileStorage,

    /// List of debug display items for rendering.
    pub debug_items: Vec<DebugItem>,

    /// List of current debug messages to log on screen
    messages: Vec<DebugMessage>,

    /// Set of sub-graphs that are required, determined during visibility pass
    pub required_sub_graphs: FastHashSet<PictureIndex>,

    /// Temporary buffers for building segments in to during prepare pass
    pub quad_direct_segments: Vec<QuadSegment>,
    pub quad_color_segments: Vec<QuadSegment>,
    pub quad_indirect_segments: Vec<QuadSegment>,

    /// A retained classifier for checking which segments of a tiled primitive
    /// need a mask / are clipped / can be rendered directly
    pub quad_tile_classifier: QuadTileClassifier,
}

impl Default for PrimitiveScratchBuffer {
    fn default() -> Self {
        PrimitiveScratchBuffer {
            clip_mask_instances: Vec::new(),
            glyph_keys: GlyphKeyStorage::new(0),
            border_cache_handles: BorderHandleStorage::new(0),
            segments: SegmentStorage::new(0),
            segment_instances: SegmentInstanceStorage::new(0),
            gradient_tiles: GradientTileStorage::new(0),
            debug_items: Vec::new(),
            messages: Vec::new(),
            required_sub_graphs: FastHashSet::default(),
            quad_direct_segments: Vec::new(),
            quad_color_segments: Vec::new(),
            quad_indirect_segments: Vec::new(),
            quad_tile_classifier: QuadTileClassifier::new(),
        }
    }
}

impl PrimitiveScratchBuffer {
    pub fn recycle(&mut self, recycler: &mut Recycler) {
        recycler.recycle_vec(&mut self.clip_mask_instances);
        self.glyph_keys.recycle(recycler);
        self.border_cache_handles.recycle(recycler);
        self.segments.recycle(recycler);
        self.segment_instances.recycle(recycler);
        self.gradient_tiles.recycle(recycler);
        recycler.recycle_vec(&mut self.debug_items);
        recycler.recycle_vec(&mut self.quad_direct_segments);
        recycler.recycle_vec(&mut self.quad_color_segments);
        recycler.recycle_vec(&mut self.quad_indirect_segments);
    }

    pub fn begin_frame(&mut self) {
        // Clear the clip mask tasks for the beginning of the frame. Append
        // a single kind representing no clip mask, at the ClipTaskIndex::INVALID
        // location.
        self.clip_mask_instances.clear();
        self.clip_mask_instances.push(ClipMaskKind::None);
        self.quad_direct_segments.clear();
        self.quad_color_segments.clear();
        self.quad_indirect_segments.clear();

        self.border_cache_handles.clear();

        // TODO(gw): As in the previous code, the gradient tiles store GPU cache
        //           handles that are cleared (and thus invalidated + re-uploaded)
        //           every frame. This maintains the existing behavior, but we
        //           should fix this in the future to retain handles.
        self.gradient_tiles.clear();

        self.required_sub_graphs.clear();

        self.debug_items.clear();
    }

    pub fn end_frame(&mut self) {
        const MSGS_TO_RETAIN: usize = 32;
        const TIME_TO_RETAIN: u64 = 2000000000;
        const LINE_HEIGHT: f32 = 20.0;
        const X0: f32 = 32.0;
        const Y0: f32 = 32.0;
        let now = api::backport::precise_time_ns();

        let msgs_to_remove = self.messages.len().max(MSGS_TO_RETAIN) - MSGS_TO_RETAIN;
        let mut msgs_removed = 0;

        self.messages.retain(|msg| {
            if msgs_removed < msgs_to_remove {
                msgs_removed += 1;
                return false;
            }

            if msg.timestamp + TIME_TO_RETAIN < now {
                return false;
            }

            true
        });

        let mut y = Y0 + self.messages.len() as f32 * LINE_HEIGHT;
        let shadow_offset = 1.0;

        for msg in &self.messages {
            self.debug_items.push(DebugItem::Text {
                position: DevicePoint::new(X0 + shadow_offset, y + shadow_offset),
                color: debug_colors::BLACK,
                msg: msg.msg.clone(),
            });

            self.debug_items.push(DebugItem::Text {
                position: DevicePoint::new(X0, y),
                color: debug_colors::RED,
                msg: msg.msg.clone(),
            });

            y -= LINE_HEIGHT;
        }
    }

    pub fn push_debug_rect_with_stroke_width(
        &mut self,
        rect: WorldRect,
        border: ColorF,
        stroke_width: f32,
    ) {
        let top_edge = WorldRect::new(
            WorldPoint::new(rect.min.x + stroke_width, rect.min.y),
            WorldPoint::new(rect.max.x - stroke_width, rect.min.y + stroke_width),
        );
        self.push_debug_rect(top_edge * DevicePixelScale::new(1.0), border, border);

        let bottom_edge = WorldRect::new(
            WorldPoint::new(rect.min.x + stroke_width, rect.max.y - stroke_width),
            WorldPoint::new(rect.max.x - stroke_width, rect.max.y),
        );
        self.push_debug_rect(bottom_edge * DevicePixelScale::new(1.0), border, border);

        let right_edge = WorldRect::new(
            WorldPoint::new(rect.max.x - stroke_width, rect.min.y),
            rect.max,
        );
        self.push_debug_rect(right_edge * DevicePixelScale::new(1.0), border, border);

        let left_edge = WorldRect::new(
            rect.min,
            WorldPoint::new(rect.min.x + stroke_width, rect.max.y),
        );
        self.push_debug_rect(left_edge * DevicePixelScale::new(1.0), border, border);
    }

    #[allow(dead_code)]
    pub fn push_debug_rect(&mut self, rect: DeviceRect, outer_color: ColorF, inner_color: ColorF) {
        self.debug_items.push(DebugItem::Rect {
            rect,
            outer_color,
            inner_color,
        });
    }

    #[allow(dead_code)]
    pub fn push_debug_string(&mut self, position: DevicePoint, color: ColorF, msg: String) {
        self.debug_items.push(DebugItem::Text {
            position,
            color,
            msg,
        });
    }

    #[allow(dead_code)]
    pub fn log(&mut self, msg: String) {
        self.messages.push(DebugMessage {
            msg,
            timestamp: api::backport::precise_time_ns(),
        })
    }
}

#[derive(Clone, Debug)]
pub struct PrimitiveStoreStats {
    picture_count: usize,
    text_run_count: usize,
    image_count: usize,
    linear_gradient_count: usize,
    color_binding_count: usize,
}

impl PrimitiveStoreStats {
    pub fn empty() -> Self {
        PrimitiveStoreStats {
            picture_count: 0,
            text_run_count: 0,
            image_count: 0,
            linear_gradient_count: 0,
            color_binding_count: 0,
        }
    }
}

pub struct PrimitiveStore {
    pub pictures: Vec<PicturePrimitive>,
    pub text_runs: TextRunStorage,
    pub linear_gradients: LinearGradientStorage,

    /// A list of image instances. These are stored separately as
    /// storing them inline in the instance makes the structure bigger
    /// for other types.
    pub images: ImageInstanceStorage,

    /// animated color bindings for this primitive.
    pub color_bindings: ColorBindingStorage,
}

impl PrimitiveStore {
    pub fn new(stats: &PrimitiveStoreStats) -> PrimitiveStore {
        PrimitiveStore {
            pictures: Vec::with_capacity(stats.picture_count),
            text_runs: TextRunStorage::new(stats.text_run_count),
            images: ImageInstanceStorage::new(stats.image_count),
            color_bindings: ColorBindingStorage::new(stats.color_binding_count),
            linear_gradients: LinearGradientStorage::new(stats.linear_gradient_count),
        }
    }

    pub fn reset(&mut self) {
        self.pictures.clear();
        self.text_runs.clear();
        self.images.clear();
        self.color_bindings.clear();
        self.linear_gradients.clear();
    }

    pub fn get_stats(&self) -> PrimitiveStoreStats {
        PrimitiveStoreStats {
            picture_count: self.pictures.len(),
            text_run_count: self.text_runs.len(),
            image_count: self.images.len(),
            linear_gradient_count: self.linear_gradients.len(),
            color_binding_count: self.color_bindings.len(),
        }
    }

    #[allow(unused)]
    pub fn print_picture_tree(&self, root: PictureIndex) {
        use crate::print_tree::PrintTree;
        let mut pt = PrintTree::new("picture tree");
        self.pictures[root.0].print(&self.pictures, root, &mut pt);
    }
}

impl Default for PrimitiveStore {
    fn default() -> Self {
        PrimitiveStore::new(&PrimitiveStoreStats::empty())
    }
}

/// Trait for primitives that are directly internable.
/// see SceneBuilder::add_primitive<P>
pub trait InternablePrimitive: intern::Internable<InternData = ()> + Sized {
    /// Build a new key from self with `info`.
    fn into_key(self, info: &LayoutPrimitiveInfo) -> Self::Key;

    fn make_instance_kind(
        key: Self::Key,
        data_handle: intern::Handle<Self>,
        prim_store: &mut PrimitiveStore,
    ) -> PrimitiveInstanceKind;
}

#[test]
#[cfg(target_pointer_width = "64")]
fn test_struct_sizes() {
    use std::mem;
    // The sizes of these structures are critical for performance on a number of
    // talos stress tests. If you get a failure here on CI, there's two possibilities:
    // (a) You made a structure smaller than it currently is. Great work! Update the
    //     test expectations and move on.
    // (b) You made a structure larger. This is not necessarily a problem, but should only
    //     be done with care, and after checking if talos performance regresses badly.
    assert_eq!(
        mem::size_of::<PrimitiveInstance>(),
        88,
        "PrimitiveInstance size changed"
    );
    assert_eq!(
        mem::size_of::<PrimitiveInstanceKind>(),
        24,
        "PrimitiveInstanceKind size changed"
    );
    assert_eq!(
        mem::size_of::<PrimitiveTemplate>(),
        56,
        "PrimitiveTemplate size changed"
    );
    assert_eq!(
        mem::size_of::<PrimitiveTemplateKind>(),
        28,
        "PrimitiveTemplateKind size changed"
    );
    assert_eq!(
        mem::size_of::<PrimitiveKey>(),
        36,
        "PrimitiveKey size changed"
    );
    assert_eq!(
        mem::size_of::<PrimitiveKeyKind>(),
        16,
        "PrimitiveKeyKind size changed"
    );
}
